/**
 * Class: Space
 *
 * Purpose: Particle system runtime container
 *
 * Created: Jan 23, 2010 @ 11:57:33 PM
 * @author Ken Scott
 */
package org.openfantasia.ps;

import org.openfantasia.ps.modifier.Behavior;
import org.openfantasia.ps.modifier.Modifier;
import org.openfantasia.ps.modifier.Surface;
import org.openfantasia.ps.modifier.behaviors.BehaviorManager;
import org.openfantasia.ps.particle.Particle;
import org.openfantasia.time.Tickable;
import org.openfantasia.ui.PFrame;
import org.openfantasia.util.listener.Event;
import org.openfantasia.util.listener.Listener;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class Space implements Listener {

	protected double prevTickTime = -1;

	protected double currentTime = -1;

	protected PFrame frame;

	protected final List<Particle> particles = new LinkedList<Particle>();

	protected final List<Modifier> forces = new LinkedList<Modifier>();

	protected final List<Modifier> surfaces = new LinkedList<Modifier>();

	protected BehaviorManager behaviors = new BehaviorManager();

	protected Action setup;

	protected Action shutdown;

	protected Ticker beforeTick;

	protected Ticker afterTick;


	public Space(PFrame frame) {
		this.frame = frame;
		setup();
	}


	public PFrame getFrame() {
		return frame;
	}


	public void setup() {
	}


	public double getCurrentTime() {
		return currentTime;
	}


	public void beforeTick(double time, double deltaTime) {
		if (beforeTick != null) {
			beforeTick.tick(time, deltaTime);
		}
	}


	public void afterTick(double time, double deltaTime) {
		if (afterTick != null) {
			afterTick.tick(time, deltaTime);
		}
	}


	public Action getSetup() {
		return setup;
	}


	public void setSetup(Action setup) {
		this.setup = setup;
	}


	public Action getShutdown() {
		return shutdown;
	}


	public void setShutdown(Action shutdown) {
		this.shutdown = shutdown;
	}


	public Ticker getBeforeTick() {
		return beforeTick;
	}


	public void setBeforeTick(Ticker beforeTick) {
		this.beforeTick = beforeTick;
	}


	public Ticker getAfterTick() {
		return afterTick;
	}


	public void setAfterTick(Ticker afterTick) {
		this.afterTick = afterTick;
	}


	public void addParticle(Particle particle) {
		synchronized (particles) {
			if (!particles.contains(particle)) {
				particles.add(particle);
				particle.setSpace(this);
			}
		}
	}


	public void removeParticle(Particle particle) {
		synchronized (particles) {
			particles.remove(particle);
			particle.setSpace(null);
		}
	}


	public List<Particle> getParticles() {
		synchronized (particles) {
			return new ArrayList<Particle>(particles);
		}
	}


	public void addForce(Modifier force) {
		synchronized (forces) {
			if (!forces.contains(force)) {
				forces.add(force);
			}
		}
	}


	public void removeForce(Modifier force) {
		synchronized (forces) {
			forces.add(force);
		}
	}


	public List<Modifier> getForces() {
		synchronized (forces) {
			return new ArrayList<Modifier>(forces);
		}
	}


	public void addSurface(Modifier surface) {
		synchronized (surfaces) {
			if (!surfaces.contains(surface)) {
				surfaces.add(surface);
			}
		}
	}


	public void removeSurface(Modifier surface) {
		synchronized (surfaces) {
			surfaces.add(surface);
		}
	}


	public List<Modifier> getSurfaces() {
		synchronized (surfaces) {
			return new ArrayList<Modifier>(surfaces);
		}
	}


	public void run() {
		if (setup != null) {
			setup.action();
			setup = null;
		}

		if (!particles.isEmpty() || !surfaces.isEmpty()) {
			currentTime = frame.getTime();

			double dt = currentTime - prevTickTime;
			prevTickTime = currentTime;

			beforeTick(currentTime, dt);

			Particle[] ps = null;
			Modifier[] fs = null;
			Modifier[] ss = null;
			synchronized (particles) {
				ps = particles.toArray(new Particle[particles.size()]);
			}
			synchronized (forces) {
				fs = forces.toArray(new Modifier[forces.size()]);
			}
			synchronized (surfaces) {
				ss = surfaces.toArray(new Modifier[surfaces.size()]);
			}

			// Apply forces to particles
			for (Particle particle : ps) {
				for (Modifier f : fs) {
					f.act(particle);
				}
			}

			for (Modifier s : ss) {
				((Surface) s).draw(this.frame);
			}

			// Update the particles
			for (Particle particle : ps) {
				particle.tick(currentTime, dt);
			}

			// Operator surfaces to particles
			for (Particle particle : ps) {
				for (Modifier s : ss) {
					s.act(particle);
				}
			}

			List<Particle> particleList = copyParticles();
			for (Particle particle : particleList) {
				particle.draw();
			}

			afterTick(currentTime, dt);
		}
		else {
			if (shutdown != null) {
				shutdown.action();
				shutdown = null;
			}
		}
	}


	public List<Particle> copyParticles() {
		synchronized (this) {
			List<Particle> result = new ArrayList(getParticles());
			for (int i = 0; i < result.size(); i++) {
				result.set(i, result.get(i).copy());
			}
			return result;
		}
	}


	public void receive(Event event) {

	}


	public void addBehavior(String name, Behavior bhv) {
		behaviors.add(name, bhv);
	}


	public void removeBehavior(String name, Behavior bhv) {
		behaviors.remove(name, bhv);
	}


	public Object getBehaviorValue(String name, Particle particle, double time, Object param) {
		return behaviors.getBehaviorValue(name, particle, time, param);
	}


	public static abstract class Action {

		protected Space space;


		public Action(Space space) {
			this.space = space;
		}


		public abstract void action();
	}

	public static abstract class Ticker implements Tickable {

		protected Space space;


		public Ticker(Space space) {
			this.space = space;
		}


		public abstract void tick(double time, double deltaTime);
	}

}
